package com.wiki4j.gdo.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.wiki4j.gdo.GDOType;
import com.wiki4j.gdo.GDObject;
import com.wiki4j.gdo.GDProperty;
import com.wiki4j.gdo.GDType;
import com.wiki4j.gdo.type.GDTypeBase;

public class GDObjectImpl extends LinkedHashMap<String,Object> implements GDObject{
  public static final Pattern ArrayPattern=Pattern.compile("^([^\\[]+)(\\[\\d+\\])*$");
  private GDOType type;
  private Object value;

  public GDObjectImpl(GDOType type){
    this.type=type;
  }

  @Override
  public GDOType type(){
    return type;
  }

  @Override
  public Object get(Object propertyName){
    GDProperty child=type.getProperty((String)propertyName);
    Object src=super.get(propertyName);
    return src;
  }

  @Override
  public Object put(String propertyName,Object value){
    GDProperty child=type.getProperty(propertyName);
    GDType childType=child.getType();
    if(childType instanceof GDTypeBase){
      Object item=((GDTypeBase)childType).from(value);
      return super.put(propertyName,item);
    }else return super.put(propertyName,value);
  }

  @Override
  public Object getObject(String path){
    return EL.eval(path,this);
  }

  private void setObject(String[] segs,int index,Object value){
    String seg=segs[index];
    boolean isLast=segs.length==index+1;
    Matcher matcher=ArrayPattern.matcher(seg);
    if(isLast){
      put(seg,value);
      return;
    }
  }

  @Override
  public void setObject(String path,Object value){
    PathGrammar grammar=PathGrammar.cached(path);
    grammar.set(this,value);
  }
  
  void putObject(String key,Object value) {
    put(key,value);
  }

  @Override
  public String getString(String path){
    Object src=getObject(path);
    if(src==null) return null;
    return src.toString();
  }

  @Override
  public void setString(String path,String value){
    Object src=super.get(path);
    GDProperty child=type.getProperty(path);
    GDTypeBase childType=child.baseType();
    Object item=childType.fromString(value);
    put(path,item);
  }

  @Override
  public void addString(String path,String value){
    GDProperty child=type.getProperty(path);
    int dim=child.getArrayDim();
    if(dim<=0) throw new IllegalArgumentException("Property: "+path+" is not array!");
    if(dim>1) throw new IllegalArgumentException("Property: "+path+" is array,but dim more than one!");
    List<Object> list=(List<Object>)super.get(path);
    if(list==null){
      list=new ArrayList<Object>();
      super.put(path,list);
    }
    GDTypeBase childType=child.baseType();
    Object item=childType.fromString(value);
    list.add(item);
  }

  @Override
  public int getInt(String path){
    Object src=getObject(path);
    if(src==null) return 0;
    if(src instanceof Number) return ((Number)src).intValue();
    return Integer.parseInt(src.toString());
  }

  @Override
  public void setInt(String path,int value){
    Object src=super.get(path);
    GDProperty child=type.getProperty(path);
    GDTypeBase childType=child.baseType();
    Object item=childType.fromInt(value);
    put(path,item);
  }

  @Override
  public void addInt(String path,int value){
    GDProperty child=type.getProperty(path);
    int dim=child.getArrayDim();
    if(dim<=0) throw new IllegalArgumentException("Property: "+path+" is not array!");
    if(dim>1) throw new IllegalArgumentException("Property: "+path+" is array,but dim more than one!");
    List<Object> list=(List<Object>)super.get(path);
    if(list==null){
      list=new ArrayList<Object>();
      put(path,list);
    }
    GDTypeBase childType=child.baseType();
    Object item=childType.fromInt(value);
    list.add(item);
  }

  @Override
  public long getLong(String path){
    Object src=getObject(path);
    if(src==null) return 0L;
    if(src instanceof Number) return ((Number)src).longValue();
    return Long.parseLong(src.toString());
  }

  @Override
  public void setLong(String path,long value){
    Object src=super.get(path);
    GDProperty child=type.getProperty(path);
    GDTypeBase childType=child.baseType();
    Object item=childType.fromLong(value);
    put(path,item);
  }

  @Override
  public void addLong(String path,long value){
    GDProperty child=type.getProperty(path);
    int dim=child.getArrayDim();
    if(dim<=0) throw new IllegalArgumentException("Property: "+path+" is not array!");
    if(dim>1) throw new IllegalArgumentException("Property: "+path+" is array,but dim more than one!");
    List<Object> list=(List<Object>)super.get(path);
    if(list==null){
      list=new ArrayList<Object>();
      put(path,list);
    }
    GDTypeBase childType=child.baseType();
    Object item=childType.fromLong(value);
    list.add(item);
  }

  @Override
  public double getDouble(String path){
    Object src=getObject(path);
    if(src==null) return 0;
    if(src instanceof Number) return ((Number)src).doubleValue();
    return Double.parseDouble(src.toString());
  }

  @Override
  public void setDouble(String path,double value){
    Object src=super.get(path);
    GDProperty child=type.getProperty(path);
    GDTypeBase childType=child.baseType();
    Object item=childType.fromDouble(value);
    put(path,item);
  }

  @Override
  public void addDouble(String path,double value){
    GDProperty child=type.getProperty(path);
    int dim=child.getArrayDim();
    if(dim<=0) throw new IllegalArgumentException("Property: "+path+" is not array!");
    if(dim>1) throw new IllegalArgumentException("Property: "+path+" is array,but dim more than one!");
    List<Object> list=(List<Object>)super.get(path);
    if(list==null){
      list=new ArrayList<Object>();
      put(path,list);
    }
    GDTypeBase childType=child.baseType();
    Object item=childType.fromDouble(value);
    list.add(item);
  }

  @Override
  public boolean getBoolean(String path){
    Object src=getObject(path);
    if(src==null) return false;
    if(src instanceof Boolean) return (boolean)src;
    if(src instanceof Number) return ((Number)src).intValue()!=0;
    return Boolean.parseBoolean(src.toString());
  }

  @Override
  public void setBoolean(String path,boolean value){
    Object src=super.get(path);
    GDProperty child=type.getProperty(path);
    GDTypeBase childType=child.baseType();
    Object item=childType.fromBoolean(value);
    put(path,item);
  }

  @Override
  public void addBoolean(String path,boolean value){
    GDProperty child=type.getProperty(path);
    int dim=child.getArrayDim();
    if(dim<=0) throw new IllegalArgumentException("Property: "+path+" is not array!");
    if(dim>1) throw new IllegalArgumentException("Property: "+path+" is array,but dim more than one!");
    List<Object> list=(List<Object>)super.get(path);
    if(list==null){
      list=new ArrayList<Object>();
      put(path,list);
    }
    GDTypeBase childType=child.baseType();
    Object item=childType.fromBoolean(value);
    list.add(item);
  }

  @Override
  public BigDecimal getBigDecimal(String path){
    Object src=getObject(path);
    if(src==null) return null;
    if(src instanceof BigDecimal) return (BigDecimal)src;
    if(src instanceof Double) return BigDecimal.valueOf((double)src);
    if(src instanceof Long) return BigDecimal.valueOf((long)src);
    if(src instanceof Integer) return BigDecimal.valueOf((int)src);
    if(src instanceof Number) return BigDecimal.valueOf(((Number)src).longValue());
    return new BigDecimal(src.toString());
  }

  @Override
  public void setBigDecimal(String path,BigDecimal value){
    Object src=super.get(path);
    GDProperty child=type.getProperty(path);
    GDTypeBase childType=child.baseType();
    Object item=childType.fromBigDecimal(value);
    put(path,item);
  }

  @Override
  public void addBigDecimal(String path,BigDecimal value){
    GDProperty child=type.getProperty(path);
    int dim=child.getArrayDim();
    if(dim<=0) throw new IllegalArgumentException("Property: "+path+" is not array!");
    if(dim>1) throw new IllegalArgumentException("Property: "+path+" is array,but dim more than one!");
    List<Object> list=(List<Object>)super.get(path);
    if(list==null){
      list=new ArrayList<Object>();
      put(path,list);
    }
    GDTypeBase childType=child.baseType();
    Object item=childType.fromBigDecimal(value);
    list.add(item);
  }

  @Override
  public GDObject getGDObject(String path){
    Object src=getObject(path);
    return (GDObject)src;
  }

  @Override
  public void setGDObject(String path,GDObject value){
    Object src=super.get(path);
    GDProperty child=type.getProperty(path);
    GDOType childType=child.gdoType();
    Object item=childType.fromGDObject(value);
    put(path,item);
  }

  @Override
  public void addGDObject(String path,GDObject value){
    GDProperty child=type.getProperty(path);
    int dim=child.getArrayDim();
    if(dim<=0) throw new IllegalArgumentException("Property: "+path+" is not array!");
    if(dim>1) throw new IllegalArgumentException("Property: "+path+" is array,but dim more than one!");
    List<Object> list=(List<Object>)super.get(path);
    if(list==null){
      list=new ArrayList<Object>();
      put(path,list);
    }
    GDOType childType=child.gdoType();
    Object item=childType.fromGDObject(value);
    list.add(item);
  }

  @Override
  public List<?> getList(String path){
    Object src=getObject(path);
    return (List<?>)src;
  }

  @Override
  public void setList(String path,List<?> value){
    Object src=super.get(path);
    GDProperty child=type.getProperty(path);
    GDOType childType=child.gdoType();
    Object item=childType.fromList(value);
    put(path,item);
  }

  @Override
  public void addList(String path,List<?> value){
    GDProperty child=type.getProperty(path);
    int dim=child.getArrayDim();
    if(dim<=0) throw new IllegalArgumentException("Property: "+path+" is not array!");
    if(dim>1) throw new IllegalArgumentException("Property: "+path+" is array,but dim more than one!");
    List<Object> list=(List<Object>)super.get(path);
    if(list==null){
      list=new ArrayList<Object>();
      super.put(path,list);
    }
    GDOType childType=child.gdoType();
    Object item=childType.fromList(value);
    list.add(item);
  }

  @Override
  public GDObject createGDObject(String path,int...indexes){
    GDProperty child=type.getProperty(path);
    int dim=child.getArrayDim();
    if(dim!=0) throw new IllegalArgumentException("Property: "+path+" is an array!");
    GDOType childType=child.gdoType();
    GDObject gdo=childType.create();
    put(path,gdo);
    return gdo;
  }

  @Override
  public String toJson(){
    return JSON.toJson(this);
  }

  @Override
  public String toString(){
    return toJson();
  }
}
